Skip to content

[management] Move service reload outside transaction in account settings update#5325

Merged
mlsmaycon merged 3 commits intomainfrom
fix/account-update
Feb 14, 2026
Merged

[management] Move service reload outside transaction in account settings update#5325
mlsmaycon merged 3 commits intomainfrom
fix/account-update

Conversation

@bcmmbaga
Copy link
Copy Markdown
Contributor

@bcmmbaga bcmmbaga commented Feb 14, 2026

Describe your changes

Issue ticket number and link

fixes #5318

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • Bug Fixes

    • Network and DNS updates now defer service and reverse-proxy reloads until after account updates complete, preventing inconsistent proxy state and race conditions.
  • Chores

    • Removed automatic peer/broadcast updates immediately following bulk service reloads.
  • Tests

    • Added a test ensuring network-range changes complete without deadlock.

Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

Defers reverse-proxy reloads during account updates by introducing a local reloadReverseProxy flag; skips immediate reloads when network-related settings change and invokes ReloadAllServicesForAccount after the transaction. Also removes a post-reload UpdateAccountPeers call from the reverse-proxy manager. Adds a test ensuring no deadlock on NetworkRange changes.

Changes

Cohort / File(s) Summary
Account update flow
management/server/account.go
Adds local reloadReverseProxy flag; when NetworkRange or related network fields change, set the flag instead of calling ReloadAllServicesForAccount immediately. After transaction commit, conditionally calls ReloadAllServicesForAccount and emits post-event reloads for DNS/network changes.
Reverse proxy manager behavior
management/internals/modules/reverseproxy/manager/manager.go
Removes the UpdateAccountPeers call at the end of ReloadAllServicesForAccount, so peer update/broadcast no longer runs after reloading services for an account.
Tests
management/server/account_test.go
Adds TestDefaultAccountManager_UpdateAccountSettings_NetworkRangeChange to ensure changing NetworkRange during UpdateAccountSettings does not deadlock (runs update in goroutine with timeout).

Sequence Diagram(s)

sequenceDiagram
    participant API as Account API
    participant DB as Database
    participant Event as Event Dispatcher
    participant RP as ReverseProxyManager
    participant Peers as PeerManager

    API->>DB: Begin account update transaction
    DB-->>API: Ack
    API->>DB: Update network fields (e.g., NetworkRange)
    DB-->>API: Ack
    API->>API: set reloadReverseProxy = true
    API->>DB: Commit transaction
    DB-->>API: Commit ack
    API->>Event: emit account update events (DNS/network)
    Event->>RP: notify of network/DNS change
    RP->>RP: ReloadAllServicesForAccount (performed after tx)
    Note right of RP: previously would call\nPeers.UpdateAccountPeers() here (removed)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • pascal-fischer

Poem

🐰 I nudged a flag, let reloads wait,
Transactions finish, then the proxies wake,
No hurried broadcasts, no clumsy peers,
A patient hop that calms the gears—
Quiet networks, carrot-quiet state.

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description follows the template structure with issue ticket, checklist completed, and documentation decision made, but lacks detail about the actual changes being made. Add a detailed explanation under 'Describe your changes' section explaining what problem was fixed, why moving the reload outside the transaction solves it, and the technical approach taken.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: moving service reload outside a transaction in account settings update, which aligns with the code changes.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/account-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@management/server/account.go`:
- Around line 396-404: The guard using reloadReverseProxy prevents
UpdateAccountPeers from running when
reverseProxyManager.ReloadAllServicesForAccount(ctx, accountID) fails; change
the logic in the block around ReloadAllServicesForAccount so that on error you
reset reloadReverseProxy to false (or otherwise ensure the fallback path runs) —
i.e., after calling am.reverseProxyManager.ReloadAllServicesForAccount(...), if
err != nil log the error as before and set reloadReverseProxy = false so the
subsequent if !reloadReverseProxy && (updateAccountPeers || extraSettingsChanged
|| groupChangesAffectPeers) will invoke am.UpdateAccountPeers(ctx, accountID).
Ensure you only flip the flag on failure and keep successful reloads unchanged.
- Around line 396-400: The call to
am.reverseProxyManager.ReloadAllServicesForAccount(...) in UpdateAccountSettings
is not nil-safe; add a defensive nil-check around am.reverseProxyManager (e.g.,
if am.reverseProxyManager != nil { ... }) before calling
ReloadAllServicesForAccount, or alternatively document/validate that
SetServiceManager must be called during initialization so reverseProxyManager is
guaranteed non-nil; prefer adding the nil guard to make UpdateAccountSettings
robust in tests and alternative init flows.

Comment thread management/server/account.go
Comment on lines +396 to +400
if reloadReverseProxy {
if err = am.reverseProxyManager.ReloadAllServicesForAccount(ctx, accountID); err != nil {
log.WithContext(ctx).Warnf("failed to reload all services for account %s: %v", accountID, err)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how reverseProxyManager / SetServiceManager is initialized and where it's called
echo "=== SetServiceManager call sites ==="
rg -n 'SetServiceManager' --type go -C 3

echo ""
echo "=== reverseProxyManager usage ==="
rg -n 'reverseProxyManager' --type go -C 2

echo ""
echo "=== ReloadAllServicesForAccount interface definition ==="
ast-grep --pattern $'type Manager interface {
  $$$
  ReloadAllServicesForAccount($$$) $$$
  $$$
}'

Repository: netbirdio/netbird

Length of output: 29813


🏁 Script executed:

#!/bin/bash
# Check the initialization lifecycle and AfterInit pattern
echo "=== AfterInit callback pattern in modules.go ==="
sed -n '100,115p' management/internals/server/modules.go

echo ""
echo "=== BuildManager function (account manager creation) ==="
sed -n '95,130p' management/internals/server/modules.go

echo ""
echo "=== UpdateAccountSettings function signature ==="
rg -n 'func.*UpdateAccountSettings' --type go -A 2

echo ""
echo "=== How UpdateAccountSettings is typically called (checking for direct calls) ==="
rg -n 'UpdateAccountSettings' --type go | grep -v '_test.go' | head -20

Repository: netbirdio/netbird

Length of output: 5963


🏁 Script executed:

#!/bin/bash
# Check the BaseServer initialization lifecycle and AfterInit timing
echo "=== BaseServer structure and AfterInit definition ==="
rg -n 'type BaseServer' --type go -A 20 | head -40

echo ""
echo "=== AfterInit method implementation ==="
rg -n 'func.*AfterInit' --type go -B 2 -A 10

echo ""
echo "=== Server startup sequence - when handlers/HTTP listeners start ==="
rg -n 'func.*Start\|func.*Run' management/internals/server/base_server.go --type go -B 2 -A 5 | head -60

echo ""
echo "=== Check accounts_handler.go context and registration ==="
sed -n '1,50p' management/server/http/handlers/accounts/accounts_handler.go

Repository: netbirdio/netbird

Length of output: 4607


🏁 Script executed:

#!/bin/bash
# Check when afterInit callbacks are executed in Start()
echo "=== Full Start() method ==="
sed -n '92,180p' management/internals/server/server.go

echo ""
echo "=== HTTP routing/handler setup in Start() ==="
sed -n '92,250p' management/internals/server/server.go | tail -100

Repository: netbirdio/netbird

Length of output: 6922


🏁 Script executed:

#!/bin/bash
# Verify the initialization order guarantee more explicitly
echo "=== APIHandler creation in Start() ==="
rg -n 'APIHandler\(\)' management/internals/server/server.go -B 5 -A 5

echo ""
echo "=== When APIHandler is created and when HTTP listener starts accepting requests ==="
sed -n '150,170p' management/internals/server/server.go

Repository: netbirdio/netbird

Length of output: 1460


Add nil guard or document the initialization guarantee for reverseProxyManager.

The code at lines 396–400 calls am.reverseProxyManager.ReloadAllServicesForAccount() without a nil check. While the server initialization framework guarantees that SetServiceManager is called via AfterInit before the HTTP listener starts accepting requests (and thus before UpdateAccountSettings can be invoked), the lack of defensive nil checks creates fragility if the code is refactored, reused in different initialization contexts, or in test scenarios. Adding a nil guard (e.g., if am.reverseProxyManager != nil { ... }) would improve robustness, particularly given that other code paths in the codebase also use reverseProxyManager without similar guards.

🤖 Prompt for AI Agents
In `@management/server/account.go` around lines 396 - 400, The call to
am.reverseProxyManager.ReloadAllServicesForAccount(...) in UpdateAccountSettings
is not nil-safe; add a defensive nil-check around am.reverseProxyManager (e.g.,
if am.reverseProxyManager != nil { ... }) before calling
ReloadAllServicesForAccount, or alternatively document/validate that
SetServiceManager must be called during initialization so reverseProxyManager is
guaranteed non-nil; prefer adding the nil guard to make UpdateAccountSettings
robust in tests and alternative init flows.

Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com>
Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

@mlsmaycon mlsmaycon merged commit 68c481f into main Feb 14, 2026
59 of 63 checks passed
@mlsmaycon mlsmaycon deleted the fix/account-update branch February 14, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hangs on save the custom network range option

2 participants